package com.gotokeep.gyromousedemo

import android.app.Activity
import android.app.Application
import android.app.Application.ActivityLifecycleCallbacks
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.text.format.DateUtils
import android.util.Log
import android.view.Gravity
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
import android.view.ViewGroup
import android.view.WindowManager
import android.widget.FrameLayout
import android.widget.Toast
import androidx.annotation.RequiresApi
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.coroutines.resume
import kotlin.math.tan

@OptIn(DelicateCoroutinesApi::class)
object CursorWindow {

    private val windowManager by lazy {
        AppContext.appContext.getSystemService(Context.WINDOW_SERVICE) as WindowManager
    }

    private val view by lazy {
        WindowView()
    }

    private val cursor: View by lazy {
        view.findViewById(R.id.imgCursor)
    }

    private val dataReceiver: DataReceiver by lazy {
        DataReceiver
    }

    private val layoutParams = WindowManager.LayoutParams().apply {
        width = ViewGroup.LayoutParams.WRAP_CONTENT
        height = ViewGroup.LayoutParams.WRAP_CONTENT
        // 默认初始居中显示
        gravity = Gravity.CENTER
        type = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
        } else {
            WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW
        }
        flags = WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS or
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
            WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
    }

    private val callback: DataCallback = label@{ data: ReceivedData ->
        if (!toContinue(data)) {
            return@label
        }
        lastTimeStamp = System.currentTimeMillis()
        lastData = data
        timeOutJob?.cancel()
        timeOutJob = GlobalScope.launch {
            delay(DURATION_TIME_OUT_IN_MILLIS)
            removeCursor()
        }
        GlobalScope.launch {
            val start = System.currentTimeMillis()
            withContext(Dispatchers.Main) {
                showCursor()
            }
            val result = handleInputData(data)
            val eventType = result.first
            val coordinates = result.second

            val xPosition = coordinates[0]
            val yPosition = coordinates[1]
            Log.e(TAG, "frameRate->receive data->$xPosition->$yPosition")
            withContext(Dispatchers.Main) {
                updateCursorPosition(xPosition, yPosition)
                onClickEvent(eventType, xPosition, yPosition)
            }
            val consumed = System.currentTimeMillis() - start
            Log.e(TAG, "time used->$consumed")
        }
    }

    private val activityCallback: ActivityCallback by lazy {
        ActivityCallback()
    }

    private var isShowing = AtomicBoolean()

    private var timeOutJob: Job? = null

    private var lastData: ReceivedData? = null
    private var lastTimeStamp = 0L

    fun init() {
        dataReceiver.dataCallback = callback
        (AppContext.appContext as Application).registerActivityLifecycleCallbacks(activityCallback)
    }

    fun showCursor() {
        if (!checkPermission() || isShowing() || !canShow()) {
            return
        }
        isShowing.set(true)

        windowManager.addView(view, layoutParams)
    }

    fun isShowing(): Boolean {
        return isShowing.get()
    }

    fun removeCursor() {
        if (isShowing()) {
            windowManager.removeView(view)
            isShowing.set(false)
        }
    }

    fun updateCursorPosition(xPosition: Float, yPosition: Float) {
        Log.e(TAG, "updateCursorPosition....")
        // cursor.x = xPosition
        // cursor.y = yPosition
        windowManager.updateViewLayout(
            view,
            layoutParams.apply {
                gravity = Gravity.TOP or Gravity.START
                x = xPosition.toInt()
                y = yPosition.toInt()
            }
        )
    }

    suspend fun onClickEvent(
        eventType: Int,
        xPosition: Float,
        yPosition: Float,
    ) {
        if (eventType != EventType.TYPE_CLICK.ordinal && eventType != EventType.TYPE_LONG_CLICK.ordinal) {
            return
        }
        Log.e(TAG, "onClickEvent->$eventType->${activityCallback.currentTopActivity}")
        val isLongClick = eventType == EventType.TYPE_LONG_CLICK.ordinal
        activityCallback.currentTopActivity?.let {
            val height = viewDimension(view).second
            val y = yPosition + height / 2

            if (dispatchTouchEvent(xPosition, y, it, isLongClick)) {
                return@let
            }

            val topLeftX = xPosition - CLICK_HORIZONTAL_RANGE / 2
            val topLeftY = y - CLICK_VERTICAL_RANGE / 2
            if (dispatchTouchEvent(topLeftX, topLeftY, it, isLongClick)) {
                return@let
            }

            val topRightX = xPosition + CLICK_HORIZONTAL_RANGE / 2
            val topRightY = y - CLICK_VERTICAL_RANGE / 2
            if (dispatchTouchEvent(topRightX, topRightY, it, isLongClick)) {
                return@let
            }

            val bottomLeftX = xPosition - CLICK_HORIZONTAL_RANGE / 2
            val bottomLeftY = y + CLICK_VERTICAL_RANGE / 2
            if (dispatchTouchEvent(bottomLeftX, bottomLeftY, it, isLongClick)) {
                return@let
            }
            val bottomRightX = xPosition + CLICK_HORIZONTAL_RANGE / 2
            val bottomRightY = y + CLICK_VERTICAL_RANGE / 2
            dispatchTouchEvent(bottomRightX, bottomRightY, it, isLongClick)
        }
    }

    suspend fun viewDimension(view: View): Pair<Int, Int> {
        val width = view.width
        val height = view.height
        if (width > 0 && height > 0) {
            return width to height
        }
        return suspendCancellableCoroutine {
            view.post {
                it.resume(view.width to view.height)
            }
        }
    }

    private fun canShow(): Boolean {
        return activityCallback.currentTopActivity != null
    }

    /**
     * 向 activity 分发事件
     * @return 事件被消费了，返回 true
     */
    private suspend fun dispatchTouchEvent(
        x: Float,
        y: Float,
        activity: Activity,
        isLongClick: Boolean = false
    ): Boolean {
        val downTime = System.currentTimeMillis()
        val downEvent = MotionEvent.obtain(
            downTime,
            downTime,
            MotionEvent.ACTION_DOWN,
            x,
            y,
            0
        )
        val downConsumed = activity.dispatchTouchEvent(downEvent)
        downEvent.recycle()
        if (isLongClick) {
            delay(ViewConfiguration.getLongPressTimeout().toLong())
        }
        val upEvent = MotionEvent.obtain(
            downTime,
            downTime + 10,
            MotionEvent.ACTION_UP,
            x,
            y,
            0
        )
        val upConsumed = activity.dispatchTouchEvent(upEvent)
        upEvent.recycle()
        return downConsumed && upConsumed
    }

    private fun checkPermission(): Boolean {
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(
                AppContext.appContext
            )
        ) {
            requestSystemWindowPermission()
            false
        } else {
            true
        }
    }

    @RequiresApi(Build.VERSION_CODES.M)
    private fun requestSystemWindowPermission() {
        try {
            val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION)
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
            val appContext = AppContext.appContext
            intent.data = Uri.parse("package:" + appContext.packageName)
            appContext.startActivity(intent)
        } catch (e: Exception) {
            GlobalScope.launch(Dispatchers.Main) {
                Toast.makeText(AppContext.appContext, "请求系统弹窗权限失败！", Toast.LENGTH_LONG).show()
            }
        }
    }

    /**
     * x + z 方向 [-Pi/2, Pi/2]
     * 向上 逆时针旋转 为 +
     * 向下 顺时针旋转 为 -
     * y -> 顺 正 逆 负
     * 规则： 以刚进入时的初始角度为标准，此时光标位于屏幕中心，当超出了边界后，远离中心的舍弃，靠近中心的则视为有效数据
     * @return [Pair.first]表示事件类型，[Pair.second]表示的是水平和垂直方向的位移，左上角为坐标原点
     */
    private suspend fun handleInputData(receivedData: ReceivedData): Pair<Int, List<Float>> {
        val eventType = receivedData.eventType
        val dataArray = receivedData.positions
        Log.e(TAG, "received data->${dataArray.joinToString()}")
        val dimensions = viewDimension(cursor)
        val xRad = dataArray.first()
        val yRange = AppContext.screenHeight - dimensions.second
        // 绕 x 轴旋转，表示的是纵向位移
        var xVertical = yRange.toFloat() / 2 -
            calculateMinus(
                xRad,
                AppContext.VERTICAL_RAD_RANGE.toFloat() / 2,
                yRange.toFloat() / 2
            )
        xVertical = xVertical.coerceAtMost((AppContext.screenHeight - dimensions.second).toFloat())
        val zRad = dataArray.last()
        // 绕 z 轴旋转，表示横向位移
        val xRange = AppContext.screenWidth - dimensions.first
        var xHorizontal = xRange.toFloat() / 2 -
            calculateMinus(
                zRad,
                AppContext.HORIZONTAL_RAD_RANGE.toFloat() / 2,
                xRange.toFloat() / 2
            )

        xHorizontal =
            xHorizontal.coerceAtMost((AppContext.screenWidth - dimensions.first).toFloat())
        return eventType to listOf(xHorizontal, xVertical)
    }

    /**
     * 计算偏移量，注意：这里不是线性变化的
     * 根据角度变化，计算在屏幕上的位置变化
     */
    private fun calculateMinus(currentRad: Float, maxRad: Float, maxDimension: Float): Float {
        return tan(currentRad) * (maxDimension / (tan(maxRad)))
    }

    /**
     * 防止刷新频率过高 + 防止微小的改动引起 UI 不必要的刷新
     */
    private fun toContinue(data: ReceivedData): Boolean {
        // 点击事件继续下面的逻辑
        if (data.eventType != EventType.TYPE_MOVE.ordinal) {
            return true
        }
        // 如果时间过短，return false
        val currentTimeMillis = System.currentTimeMillis()
        if (currentTimeMillis - lastTimeStamp < MIN_INTERVAL_IN_MILLIS) {
            return false
        }
        return lastData?.validChange(data.positions) ?: true
    }

    private class ActivityCallback : ActivityLifecycleCallbacks {

        var currentTopActivity: Activity? = null
            private set

        override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) = Unit

        override fun onActivityStarted(activity: Activity) {
            currentTopActivity = activity
        }

        override fun onActivityResumed(activity: Activity) = Unit

        override fun onActivityPaused(activity: Activity) = Unit

        override fun onActivityStopped(activity: Activity) {
            // 表示 app 切换到了后台
            if (activity == currentTopActivity) {
                currentTopActivity = null
                removeCursor()
            }
        }

        override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) = Unit

        override fun onActivityDestroyed(activity: Activity) = Unit
    }

    private class WindowView : FrameLayout(AppContext.appContext) {

        init {
            val appContext = AppContext.appContext
            val realView = LayoutInflater.from(appContext).inflate(R.layout.layout_window, null)
            setBackgroundColor(Color.TRANSPARENT)
            addView(realView)
        }

        override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
            Log.e(TAG, "dispatchKeyEvent->${event?.action}")
            if (event?.action == KeyEvent.KEYCODE_BACK || event?.action == KeyEvent.KEYCODE_SOFT_LEFT) {
                removeCursor()
                return true
            }
            return super.dispatchKeyEvent(event)
        }
    }

    private const val TAG = "CursorWindow"

    /**
     * 点击事件水平范围
     */
    private const val CLICK_HORIZONTAL_RANGE = 20

    /**
     * 点击事件垂直范围
     */
    private const val CLICK_VERTICAL_RANGE = 20

    /**
     * 鼠标输入在一段时间没有后则鼠标自动消失
     */
    private const val DURATION_TIME_OUT_IN_MILLIS = 3 * DateUtils.SECOND_IN_MILLIS

    /**
     * UI 刷新的最小间隔
     */
    private const val MIN_INTERVAL_IN_MILLIS = 15
}
